package com.cloudlead.common.persistence.impl;

import com.cloudlead.common.lang.BusinessModule;
import com.cloudlead.common.lang.Messages;
import com.cloudlead.common.lang.exception.ServerErrorException;
import com.cloudlead.common.lang.persistence.*;
import com.cloudlead.common.lang.serialize.BeanSerializeException;
import com.cloudlead.common.lang.serialize.JSON;
import com.cloudlead.common.lang.service.CrudService;
import com.cloudlead.common.persistence.AssociatedTable;
import com.cloudlead.common.persistence.CrudRepository;
import com.cloudlead.common.persistence.DynamicSpecifications;
import com.cloudlead.common.persistence.Specification;
import com.cloudlead.common.persistence.bean.AssociatedDesc;
import com.cloudlead.common.persistence.bean.BeanConvertUtils;
import com.mysema.query.BooleanBuilder;
import com.mysema.query.Tuple;
import com.mysema.query.sql.RelationalPath;
import com.mysema.query.types.OrderSpecifier;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.expr.SimpleExpression;
import com.mysema.query.types.path.PathBuilder;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.Map.Entry;

public abstract class AbstractMultiTableCrudService<Repository extends CrudRepository<Entity, String>, VO extends IdEntity<String>, Entity extends IdEntity<String>>
        extends AbstractCrudService<Repository, VO, Entity> {

    public AbstractMultiTableCrudService(BusinessModule module,
                                         Repository repository) {
        super(module, repository);
    }

    private static final int EXPIRE_SECONDS = 3600;

    private static final String CACHE_KEY_SEPARATOR = "_";

    @Resource
    protected ApplicationContext ctx;

    @SuppressWarnings("unchecked")
    @Override
    public VO view(String id) {
        Assert.notNull(id, "view id is required.");
        if (hasCache && null != cacheService) {
            Object value = cacheService.get(id);
            if (null != value) {
                return (VO) value;
            }
        }

        VO vo;
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            Tuple findOne = repository.findOne(tables, builder.get(ID).eq(id));
            vo = convertWithBelongsToTuple(tables, findOne, id);
        } else {
            vo = super.view(id);
        }
        if (null != vo && voBeanDesc.hasHasMany()) {
            for (AssociatedDesc associatedDesc : voBeanDesc.getHasManys()) {
                handleHasManyQuery(vo, associatedDesc);
            }
        }
        if (hasCache && null != cacheService && null != vo) {
            cacheService.set(vo.getId(), vo, EXPIRE_SECONDS);
        }
        return vo;
    }

    @SuppressWarnings("unchecked")
    @Override
    public VO findOne(Filter filter) {
        String md5Hex = null;
        if (hasCache && null != cacheService) {
            md5Hex = DigestUtils.md5Hex(this.getModule()
                    + CACHE_KEY_SEPARATOR + "findOne"
                    + CACHE_KEY_SEPARATOR
                    + (null == filter ? "" : filter.toString()));
            Object value = cacheService.get(md5Hex);
            if (null != value) {
                Object o = cacheService.get((String) value);
                if (null != o) {
                    return (VO) o;
                }
            }
        }

        VO vo;
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = null;
            if (null != filter) {
                predicates = new ArrayList<Predicate>();
                predicates.add(convertFilter(filter));
            }
            Tuple findOne = repository.findOne(
                    tables,
                    null == predicates ? null : predicates
                            .toArray(new Predicate[predicates.size()]));
            vo = convertWithBelongsToTuple(tables, findOne, md5Hex);
        } else {
            vo = super.findOne(filter);
        }
        if (null != vo && voBeanDesc.hasHasMany()) {
            for (AssociatedDesc associatedDesc : voBeanDesc.getHasManys()) {
                handleHasManyQuery(vo, associatedDesc);
            }
        }

        if (hasCache && null != cacheService && null != vo) {
            cacheService.set(vo.getId(), vo, EXPIRE_SECONDS);
            cacheService.set(md5Hex, vo.getId(), EXPIRE_SECONDS);
            putToClearDatas(this.getModule().getFullName(), md5Hex);
        }
        return vo;
    }

    @SuppressWarnings("unchecked")
    @Override
    public VO findOne(VO example) {
        String md5Hex = null;
        if (hasCache && null != cacheService) {
            String json = "";
            try {
                json = JSON.toString(example);
            } catch (BeanSerializeException e) {
                e.printStackTrace();
            }
            md5Hex = DigestUtils.md5Hex(this.getModule()
                    + CACHE_KEY_SEPARATOR + "findOneExample"
                    + CACHE_KEY_SEPARATOR + json);
            Object value = cacheService.get(md5Hex);
            if (null != value) {
                Object o = cacheService.get((String) value);
                if (null != o) {
                    return (VO) o;
                }
            }
        }
        VO vo;
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = null;
            if (null != example) {
                predicates = new ArrayList<Predicate>();
                predicates.add(convertFilter(getFilter(example, null)));
            }
            Tuple findOne = repository.findOne(
                    tables,
                    null == predicates ? null : predicates
                            .toArray(new Predicate[predicates.size()]));
            vo = convertWithBelongsToTuple(tables, findOne, md5Hex);
        } else {
            vo = super.findOne(example);
        }
        if (null != vo && voBeanDesc.hasHasMany()) {
            for (AssociatedDesc associatedDesc : voBeanDesc.getHasManys()) {
                handleHasManyQuery(vo, associatedDesc);
            }
        }
        if (hasCache && null != cacheService && null != vo) {
            cacheService.set(vo.getId(), vo, EXPIRE_SECONDS);
            cacheService.set(md5Hex, vo.getId(), EXPIRE_SECONDS);
            putToClearDatas(this.getModule().getFullName(), md5Hex);
        }
        return vo;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private void handleHasManyQuery(VO vo, AssociatedDesc associatedDesc) {
        try {
            CrudRepository crudRepository = findRepository((Class) associatedDesc
                    .getAssociatedClass());
            RelationalPath<?> relationalPath = entityPathResolver
                    .createPath(BeanConvertUtils.getVOBeanDesc(
                            associatedDesc.getAssociatedClass())
                            .getEntityClass());
            SimpleExpression property = (SimpleExpression) entityPathResolver
                    .getProperty(relationalPath,
                            associatedDesc.getAssociatedProperty());
            List findAll = crudRepository.findAll(property.eq(vo.getId()));
            List voList = BeanConvertUtils.convertDomainToVO(findAll,
                    associatedDesc.getAssociatedClass());
            FieldUtils.writeDeclaredField(vo, associatedDesc.getProperty(),
                    voList, true);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServerErrorException(Messages.getMessage("persistence.query.hasMany.error"), e);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<VO> list(Filter filter, Sort sort) {
        String md5Hex = null;
        if (hasCache && null != cacheService) {
            md5Hex = DigestUtils.md5Hex(this.getModule()
                    + CACHE_KEY_SEPARATOR + "list" + CACHE_KEY_SEPARATOR
                    + (null == filter ? "" : filter.toString())
                    + CACHE_KEY_SEPARATOR
                    + (null == sort ? "" : sort.toString()));

            Object value = cacheService.get(md5Hex);
            if (null != value) {
                List<String> transitList = (List<String>) value;
                List<VO> voList = new ArrayList<>();
                boolean clearKey = false;
                for (String id : transitList) {
                    Object o = cacheService.get(id);
                    if (null == o) {
                        clearKey = true;
                    } else {
                        voList.add((VO) o);
                    }
                }
                if (!clearKey) {
                    return voList;
                }
            }

        }

        List<VO> answer = new ArrayList<>();
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = null;
            if (null != filter) {
                predicates = new ArrayList<Predicate>();
                predicates.add(convertFilter(filter));
            }
            OrderSpecifier[] orders = null;
            // 处理排序
            if (null != sort) {
                List<OrderSpecifier> orderList = convertSort(sort);
                orders = orderList.toArray(new OrderSpecifier[orderList.size()]);
            }
            List<Tuple> findAll = repository
                    .findAll(tables, predicates, orders);
            for (Tuple tuple : findAll) {
                VO vo = convertWithBelongsToTuple(tables, tuple, md5Hex);
                answer.add(vo);
            }
        } else {
            answer = super.list(filter, sort);
        }
        for (VO vo : answer) {
            if (null != vo && voBeanDesc.hasHasMany()) {
                for (AssociatedDesc associatedDesc : voBeanDesc.getHasManys()) {
                    handleHasManyQuery(vo, associatedDesc);
                }
            }
        }

        if (hasCache && null != cacheService) {
            List<String> keys = new ArrayList<String>();
            for (VO vo : answer) {
                keys.add(vo.getId());
                cacheService.set(vo.getId(), vo, EXPIRE_SECONDS);
            }
            cacheService.set(md5Hex, keys, EXPIRE_SECONDS);
            putToClearDatas(this.getModule().getFullName(), md5Hex);
        }
        return answer;
    }

    @Override
    public long count(VO example) {
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = null;
            if (null != example) {
                predicates = new ArrayList<Predicate>();
                predicates.add(convertFilter(getFilter(example, null)));
            }
            long count = repository.count(tables, null == predicates ? null
                    : predicates.toArray(new Predicate[predicates.size()]));
            return count;
        }
        return super.count(example);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<VO> list(VO example, Sort sort) {
        String md5Hex = null;
        if (hasCache && null != cacheService) {
            String json = "";
            try {
                json = JSON.toString(example);
            } catch (BeanSerializeException e) {
                e.printStackTrace();
            }
            md5Hex = DigestUtils.md5Hex(this.getModule()
                    + CACHE_KEY_SEPARATOR + "listExample"
                    + CACHE_KEY_SEPARATOR + json);

            Object value = cacheService.get(md5Hex);
            if (null != value) {
                List<String> transitList = (List<String>) value;
                List<VO> voList = new ArrayList<>();
                boolean clearKey = false;
                for (String id : transitList) {
                    Object o = cacheService.get(id);
                    if (null == o) {
                        clearKey = true;
                    } else {
                        voList.add((VO) o);
                    }
                }
                if (!clearKey) {
                    return voList;
                }
            }
        }

        List<VO> answer = new ArrayList<>();
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = null;
            if (null != example) {
                predicates = new ArrayList<Predicate>();
                predicates.add(convertFilter(getFilter(example, null)));
            }
            OrderSpecifier[] orders = null;
            // 处理排序
            if (null != sort) {
                List<OrderSpecifier> orderList = convertSort(sort);
                orders = orderList.toArray(new OrderSpecifier[orderList.size()]);
            }
            List<Tuple> findAll = repository.findAll(tables, predicates, orders);
            for (Tuple tuple : findAll) {
                VO vo = convertWithBelongsToTuple(tables, tuple, md5Hex);
                if (null != vo && voBeanDesc.hasHasMany()) {
                    for (AssociatedDesc associatedDesc : voBeanDesc
                            .getHasManys()) {
                        handleHasManyQuery(vo, associatedDesc);
                    }
                }
                answer.add(vo);
            }
        } else {
            answer = super.list(example, sort);
        }
        for (VO vo : answer) {
            if (null != vo && voBeanDesc.hasHasMany()) {
                for (AssociatedDesc associatedDesc : voBeanDesc.getHasManys()) {
                    handleHasManyQuery(vo, associatedDesc);
                }
            }
        }
        if (hasCache && null != cacheService) {
            List<String> keys = new ArrayList<String>();
            for (VO vo : answer) {
                keys.add(vo.getId());
                cacheService.set(vo.getId(), vo, EXPIRE_SECONDS);
            }
            cacheService.set(md5Hex, keys, EXPIRE_SECONDS);
            putToClearDatas(this.getModule().getFullName(), md5Hex);
        }
        return answer;
    }

    @Override
    public long count(Filter filter) {
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = null;
            if (null != filter) {
                predicates = new ArrayList<Predicate>();
                predicates.add(convertFilter(filter));
            }
            long count = repository.count(tables, null == predicates ? null
                    : predicates.toArray(new Predicate[predicates.size()]));
            return count;
        }
        return super.count(filter);
    }

    @SuppressWarnings("unchecked")
    @Override
    public Page<VO> findAll(PageRequest pageRequest) {
        Assert.notNull(pageRequest, "pageRequest is required.");
        String md5Hex = null;
        if (hasCache && null != cacheService) {
            md5Hex = DigestUtils.md5Hex(this.getModule()
                    + CACHE_KEY_SEPARATOR + "findAll"
                    + CACHE_KEY_SEPARATOR
                    + (null == pageRequest ? "" : pageRequest.toString()));

            Object value = cacheService.get(md5Hex);
            if (null != value) {
                Page<String> transitList = (Page<String>) value;
                List<VO> voList = new ArrayList<>();
                boolean clearKey = false;
                for (String id : transitList) {
                    Object o = cacheService.get(id);
                    if (null == o) {
                        clearKey = true;
                    } else {
                        voList.add((VO) o);
                    }
                }
                if (!clearKey) {
                    Page<VO> answer = new Page<>(voList, pageRequest,
                            transitList.getTotalElements());
                    return answer;
                }
            }
        }

        Page<VO> answer;
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            Filter filter = pageRequest.getFilter();
            List<Predicate> predicates = null;
            if (null != filter) {
                predicates = new ArrayList();
                predicates.add(convertFilter(filter));
            }
            OrderSpecifier[] orders = null;
            // 处理排序
            Sort sort = pageRequest.getSort();
            if (null != sort) {
                List<OrderSpecifier> orderList = convertSort(sort);
                orders = orderList.toArray(new OrderSpecifier[orderList.size()]);
            }

            long count = repository.count(tables, null == predicates ? null
                    : predicates.toArray(new Predicate[predicates.size()]));

            List<Tuple> findAll = repository.findAll(tables, predicates,
                    pageRequest.getOffset(), pageRequest.getSize(), orders);
            List<VO> list = new ArrayList<>();
            for (Tuple tuple : findAll) {
                VO vo = convertWithBelongsToTuple(tables, tuple, md5Hex);
                if (null != vo && voBeanDesc.hasHasMany()) {
                    for (AssociatedDesc associatedDesc : voBeanDesc
                            .getHasManys()) {
                        handleHasManyQuery(vo, associatedDesc);
                    }
                }
                list.add(vo);
            }
            answer = new Page(list, pageRequest, count);
        } else {
            answer = super.findAll(pageRequest);
        }
        for (VO vo : answer) {
            if (null != vo && voBeanDesc.hasHasMany()) {
                for (AssociatedDesc associatedDesc : voBeanDesc.getHasManys()) {
                    handleHasManyQuery(vo, associatedDesc);
                }
            }
        }

        if (hasCache && null != cacheService) {
            List<String> keys = new ArrayList<String>();
            for (VO vo : answer.getContent()) {
                keys.add(vo.getId());
                cacheService.set(vo.getId(), vo, EXPIRE_SECONDS);
            }

            Page<String> keyPage = new Page<>(keys, pageRequest,
                    answer.getTotalElements());
            cacheService.set(md5Hex, keyPage, EXPIRE_SECONDS);
            putToClearDatas(this.getModule().getFullName(), md5Hex);
        }
        return answer;
    }

    @Override
    @Transactional
    public void updateAll(VO vo, Filter filter) {
        Assert.notNull(vo, "update vo is required.");
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = null;
            if (null != filter) {
                predicates = new ArrayList<Predicate>();
                predicates.add(convertFilter(filter));
            }
            repository.updateAll(BeanConvertUtils.convertVOToDomain(vo,
                    entityClass), tables, null == predicates ? null
                    : predicates.toArray(new Predicate[predicates.size()]));
            if (hasCache && null != cacheService) {
                clearCachedDatas(this.getModule().getFullName());
            }
        } else {
            super.updateAll(vo, filter);
        }
    }

    @Override
    @Transactional
    public void deleteAll(Filter filter) {
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = null;
            if (null != filter) {
                predicates = new ArrayList<Predicate>();
                predicates.add(convertFilter(filter));
            }
            repository.deleteAll(
                    tables,
                    null == predicates ? null : predicates
                            .toArray(new Predicate[predicates.size()]));
            if (hasCache && null != cacheService) {
                clearCachedDatas(this.getModule().getFullName());
            }
        } else {
            super.deleteAll(filter);
        }
    }

    @Override
    @Transactional
    public void updateAll(VO vo, VO example) {
        Assert.notNull(vo, "update vo is required.");
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = new ArrayList<Predicate>();
            predicates.add(convertFilter(getFilter(example, null)));
            repository.updateAll(
                    BeanConvertUtils.convertVOToDomain(vo, entityClass),
                    tables,
                    predicates.toArray(new Predicate[predicates.size()]));
            if (hasCache && null != cacheService) {
                clearCachedDatas(this.getModule().getFullName());
            }
        } else {
            super.updateAll(vo, example);
        }
    }

    @Override
    @Transactional
    public void deleteAll(VO example) {
        if (voBeanDesc.hasBelongsTo()) {
            List<AssociatedTable> tables = getAssociatedTableFromDesc(voBeanDesc
                    .getBelongsTos());
            List<Predicate> predicates = null;
            if (null != example) {
                predicates = new ArrayList<Predicate>();
                predicates.add(convertFilter(getFilter(example, null)));
            }
            repository.deleteAll(
                    tables,
                    null == predicates ? null : predicates
                            .toArray(new Predicate[predicates.size()]));
            if (hasCache && null != cacheService) {
                clearCachedDatas(this.getModule().getFullName());
            }
        } else {
            super.deleteAll(example);
        }
    }

    private VO convertWithBelongsToTuple(List<AssociatedTable> tables,
                                         Tuple tuple, String cachedKey) {
        if (null == tuple) {
            return null;
        }
        Entity entity = tuple.get(root);
        VO vo = BeanConvertUtils.convertDomainToVO(entity, voClass);
        for (AssociatedTable associatedTable : tables) {
            Object associateEntity = tuple.get(associatedTable.getEntityPath());
            if (null != associateEntity) {
                Object associateVo = BeanConvertUtils.convertDomainToVO(
                        associateEntity, associatedTable.getAssociatedDesc()
                                .getAssociatedClass());
                if (hasCache && null != cacheService && null != associateVo) {
                    putToClearDatas(findService(associateVo.getClass()).getModule().getFullName(), cachedKey);
                }
                try {
                    FieldUtils.writeDeclaredField(vo, associatedTable
                                    .getAssociatedDesc().getProperty(), associateVo,
                            true);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new ServerErrorException(Messages.getMessage("persistence.query.belongsTo.error"), e);
                }
            }
        }
        return vo;
    }

    @SuppressWarnings("unchecked")
    <T extends IdEntity<String>> CrudService findService(Class voClass) {
        String simpleName = voClass.getSimpleName();
        CrudService crudService = (CrudService) ctx.getBean(Character
                .toLowerCase(simpleName.charAt(0))
                + simpleName.substring(1, simpleName.length()) + "Service");
        return crudService;
    }

    @SuppressWarnings("unchecked")
    <T extends IdEntity<String>> CrudRepository<T, String> findRepository(
            Class<T> entityClass) {
        String simpleName = entityClass.getSimpleName();
        CrudRepository<T, String> crudRepository = (CrudRepository<T, String>) ctx
                .getBean("e" + simpleName + "Repository");
        return crudRepository;
    }

    @SuppressWarnings("unchecked")
    List<AssociatedTable> getAssociatedTableFromDesc(
            List<AssociatedDesc> belongsTos) {
        List<AssociatedTable> tables = new ArrayList<AssociatedTable>();
        Set<String> tableNames = new HashSet<>();
        for (AssociatedDesc associatedDesc : belongsTos) {
            RelationalPath<?> relationalPath = entityPathResolver
                    .createPath(BeanConvertUtils.getVOBeanDesc(
                            associatedDesc.getAssociatedClass())
                            .getEntityClass());
            int index = 0;
            String alias = relationalPath.getTableName();
            while (tableNames.contains(alias)) {
                index++;
                alias = relationalPath.getTableName() + "_" + index;
            }
            tableNames.add(alias);
            if (0 < index) {
                relationalPath = entityPathResolver.createPath(BeanConvertUtils
                        .getVOBeanDesc(associatedDesc.getAssociatedClass())
                        .getEntityClass(), alias);
            }
            SimpleExpression<Object> associateProperty = (SimpleExpression<Object>) entityPathResolver
                    .getProperty(relationalPath,
                            associatedDesc.getAssociatedProperty());
            SimpleExpression<Object> rootProperty = (SimpleExpression<Object>) entityPathResolver
                    .getProperty(root, associatedDesc.getRootProperty());
            tables.add(new AssociatedTable(relationalPath, associateProperty
                    .eq(rootProperty), associatedDesc));
        }
        return tables;
    }

    Filter getFilter(Object example, String parentProperty) {
        Filter filter = new Filter(getCriteria(example, parentProperty));
        return filter;
    }

    Criteria getCriteria(Object example, String parentProperty) {
        Criteria criteria = new Criteria();
        Field[] fields = example.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (!Modifier.isFinal(field.getModifiers())
                    && !Modifier.isStatic(field.getModifiers())) {
                Object value = null;
                try {
                    field.setAccessible(true);
                    value = field.get(example);
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    e.printStackTrace();
                }
                if (null != value && !(value instanceof Collection)
                        && !value.getClass().isArray()) {
                    boolean isBelongsToField = false;
                    List<AssociatedDesc> belongsTos = voBeanDesc
                            .getBelongsTos();
                    for (AssociatedDesc associatedDesc : belongsTos) {
                        if (field.getName()
                                .equals(associatedDesc.getProperty())) {
                            isBelongsToField = true;
                            break;
                        }
                    }

                    if (value instanceof Enum) {
                        Enum enumv = (Enum) value;
                        value = enumv.name();
                    }

                    if (isBelongsToField) {
                        Criteria filter2 = getCriteria(value, field.getName());
                        for (Term term : filter2.getAnd()) {
                            criteria.and(term);
                        }
                    } else {
                        criteria.and(new Term((null == parentProperty ? ""
                                : parentProperty + ".") + field.getName(),
                                value));
                    }

                }
            }

        }

        return criteria;
    }

    Predicate convertFilter(Filter filter) {
        List<Map<String, Criteria>> orList = new ArrayList<>();
        for (Criteria criteria : filter.getOr()) {
            Map<String, Criteria> map = new HashMap<>();
            for (Term term : criteria.getAnd()) {
                String[] split = term.getProperty().split("\\.");
                if (split.length > 1) {
                    Criteria filter2 = map.get(split[0]);
                    if (null == filter2) {
                        filter2 = new Criteria();
                    }
                    filter2.and(new Term(split[1], term.getOperator(),
                            term.getValue()));
                    map.put(split[0], filter2);
                } else {
                    Criteria filter2 = map.get(null);
                    if (null == filter2) {
                        filter2 = new Criteria();
                    }
                    filter2.and(term);
                    map.put(null, filter2);
                }
            }
            orList.add(map);
        }

        Predicate predicate = getPredicates(orList);
        return predicate;

    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    Predicate getPredicates(List<Map<String, Criteria>> mapList) {
        BooleanBuilder orBooleanBuilder = new BooleanBuilder();
        for (Map<String, Criteria> map : mapList) {
            BooleanBuilder andBooleanBuilder = new BooleanBuilder();
            for (Entry<String, Criteria> entry : map.entrySet()) {
                if (null == entry.getKey()) {
                    andBooleanBuilder.and(convertCriteria(entityClass,
                            entry.getValue()));
                } else {
                    List<AssociatedDesc> belongsTos = voBeanDesc.getBelongsTos();
                    List<AssociatedTable> associatedTables = getAssociatedTableFromDesc(belongsTos);
                    for (AssociatedTable associatedTable : associatedTables) {
                        if (entry.getKey().equals(
                                associatedTable.getAssociatedDesc().getProperty())) {
                            RelationalPath relationalPath = associatedTable
                                    .getEntityPath();
                            PathBuilder pathBuilder = entityPathResolver
                                    .getPathBuilder(relationalPath);
                            Specification specification = DynamicSpecifications
                                    .bySearchCriteria(entry.getValue(), entityClass);
                            Predicate predicate = specification
                                    .toPredicate(relationalPath, pathBuilder,
                                            entityPathResolver);
                            andBooleanBuilder.and(predicate);
                        }
                    }
                }
            }
            orBooleanBuilder.or(andBooleanBuilder.getValue());
        }
        return orBooleanBuilder.getValue();
    }

    List<OrderSpecifier> convertSort(Sort sort) {
        Map<String, Sort> map = new HashMap<>();
        for (Sort.Order order : sort) {
            String[] split = order.getProperty().split("\\.");
            if (split.length > 1) {
                Sort sort2 = map.get(split[0]);
                if (null == sort2) {
                    sort2 = new Sort();
                }
                sort2.add(new Sort.Order(order.getDirection(), split[1]));
                map.put(split[0], sort2);
            } else {
                Sort sort2 = map.get(null);
                if (null == sort2) {
                    sort2 = new Sort();
                }
                sort2.add(order);
                map.put(null, sort2);
            }
        }

        List<OrderSpecifier> answer = new ArrayList<>();
        for (Entry<String, Sort> entry : map.entrySet()) {
            if (null == entry.getKey()) {
                Sort sort1 = entry.getValue();
                for (Sort.Order order : sort1) {
                    answer.add(toOrder(root, builder, order));
                }
            } else {
                List<AssociatedDesc> belongsTos = voBeanDesc.getBelongsTos();
                List<AssociatedTable> associatedTables = getAssociatedTableFromDesc(belongsTos);
                for (AssociatedTable associatedTable : associatedTables) {
                    if (entry.getKey().equals(
                            associatedTable.getAssociatedDesc().getProperty())) {
                        RelationalPath relationalPath = associatedTable
                                .getEntityPath();
                        PathBuilder pathBuilder = entityPathResolver
                                .getPathBuilder(relationalPath);
                        Sort sort1 = entry.getValue();
                        for (Sort.Order order : sort1) {
                            answer.add(toOrder(relationalPath, pathBuilder, order));
                        }
                    }
                }

            }
        }
        return answer;
    }

}
